package com.gitee.wsl.prop

import com.gitee.wsl.concurrent.collections.ConcurrentMutableMap

/**
 * Map of attributes accessible by [AttributeKey] in a typed manner
 */
 interface Attributes {
    /**
     * Gets a value of the attribute for the specified [key], or throws an exception if an attribute doesn't exist
     */
     operator fun <T> get(key: AttributeKey<T>): T =
        getOrNull(key) ?: throw IllegalStateException("No instance for key $key")

    /**
     * Gets a value of the attribute for the specified [key], or return `null` if an attribute doesn't exist
     */
     fun <T> getOrNull(key: AttributeKey<T>): T?

    /**
     * Checks if an attribute with the specified [key] exists
     */
     operator fun contains(key: AttributeKey<*>): Boolean

    /**
     * Creates or changes an attribute with the specified [key] using [value]
     */
     fun <T> put(key: AttributeKey<T>, value: T)

    /**
     * Removes an attribute with the specified [key]
     */
     fun <T> remove(key: AttributeKey<T>)

    /**
     * Removes an attribute with the specified [key] and returns its current value, throws an exception if an attribute doesn't exist
     */
     fun <T> take(key: AttributeKey<T>): T = get(key).also { remove(key) }

    /**
     * Removes an attribute with the specified [key] and returns its current value, returns `null` if an attribute doesn't exist
     */
     fun <T> takeOrNull(key: AttributeKey<T>): T? = getOrNull(key).also { remove(key) }

    /**
     * Gets a value of the attribute for the specified [key], or calls supplied [block] to compute its value
     */
     fun <T> computeIfAbsent(key: AttributeKey<T>, block: () -> T): T

    /**
     * Returns [List] of all [AttributeKey] instances in this map
     */
     val allKeys: List<AttributeKey<*>>

}



/**
 * Adds all attributes from another collection, replacing original values if any.
 */
 fun Attributes.putAll(other: Attributes) {
    other.allKeys.forEach {
        @Suppress("UNCHECKED_CAST")
        put(it as AttributeKey<Any>, other[it])
    }
}

fun Attributes?.mange(other: Attributes?):Attributes? {
    if(this == null && other == null) return null
    if(other == null) return this
    if(this == null) return other
    val newAttributes = Attributes()
    newAttributes.putAll(this)
    newAttributes.putAll(other)
    return newAttributes
}

/**
 * Create JVM specific attributes instance.
 */
fun Attributes(concurrent: Boolean = false): Attributes =
    if (concurrent) ConcurrentSafeAttributes() else HashMapAttributes()


private abstract class AttributesJvmBase : Attributes {
    protected abstract val map: MutableMap<AttributeKey<*>, Any?>

    @Suppress("UNCHECKED_CAST")
     final override fun <T> getOrNull(key: AttributeKey<T>): T? = map[key] as T?

     final override operator fun contains(key: AttributeKey<*>): Boolean = map.containsKey(key)

     final override fun <T> put(key: AttributeKey<T>, value: T) {
        map[key] = value
    }

     final override fun <T> remove(key: AttributeKey<T>) {
        map.remove(key)
    }

     final override val allKeys: List<AttributeKey<*>>
        get() = map.keys.toList()
}

private class ConcurrentSafeAttributes : AttributesJvmBase() {
    override val map = ConcurrentMutableMap<AttributeKey<*>, Any?>()

    /**
     * Gets a value of the attribute for the specified [key], or calls supplied [block] to compute its value.
     * Note: [block] could be eventually evaluated twice for the same key.
     * TODO: To be discussed. Workaround for android < API 24.
     */
    override fun <T> computeIfAbsent(key: AttributeKey<T>, block: () -> T): T {
        @Suppress("UNCHECKED_CAST")
        map[key]?.let { return it as T }
        val result = block()
        put(key,result)
        return (map.putIfAbsent(key, result) ?: result) as T
        //@Suppress("UNCHECKED_CAST")
        //return (map.putIfAbsent(key, result) ?: result) as T
    }
}

private class HashMapAttributes : AttributesJvmBase() {
    override val map: MutableMap<AttributeKey<*>, Any?> = HashMap()

    /**
     * Gets a value of the attribute for the specified [key], or calls supplied [block] to compute its value.
     * Note: [block] could be eventually evaluated twice for the same key.
     * TODO: To be discussed. Workaround for android < API 24.
     */
    override fun <T> computeIfAbsent(key: AttributeKey<T>, block: () -> T): T {
        @Suppress("UNCHECKED_CAST")
        map[key]?.let { return it as T }
        val result = block()
        @Suppress("UNCHECKED_CAST")
        return (map.put(key, result) ?: result) as T
    }
}